// Copyright (c) 2024 CHANGLEI. All rights reserved.

import 'dart:async';

import 'package:charts_grasp/src/chart/axis/axis.dart';
import 'package:charts_grasp/src/chart/axis/category_axis.dart';
import 'package:charts_grasp/src/chart/axis/datetime_category_axis.dart';
import 'package:charts_grasp/src/chart/base/chart_base.dart';
import 'package:charts_grasp/src/chart/chart_behavior/chart_behavior.dart';
import 'package:charts_grasp/src/chart/chart_series/series.dart';
import 'package:charts_grasp/src/chart/chart_series/series_renderer_properties.dart';
import 'package:charts_grasp/src/chart/chart_series/xy_data_series.dart';
import 'package:charts_grasp/src/chart/common/cartesian_state_properties.dart';
import 'package:charts_grasp/src/chart/common/common.dart';
import 'package:charts_grasp/src/chart/user_interaction/crosshair_painter.dart';
import 'package:charts_grasp/src/chart/utils/enum.dart';
import 'package:charts_grasp/src/chart/utils/helper.dart';
import 'package:charts_grasp/src/common/utils/helper.dart';
import 'package:flutter/material.dart';

class CrosshairBehavior {
  CrosshairBehavior({
    this.activationMode = ActivationMode.longPress,
    this.lineType = CrosshairLineType.both,
    this.lineDashArray,
    this.enable = false,
    this.lineColor,
    this.lineWidth = 1,
    this.shouldAlwaysShow = false,
    this.hideDelay = 0,
  });

  final bool enable;

  final double lineWidth;

  final Color? lineColor;

  final List<double>? lineDashArray;

  final ActivationMode activationMode;

  final CrosshairLineType lineType;

  final bool shouldAlwaysShow;

  final double hideDelay;

  @override
  bool operator ==(Object other) {
    if (identical(this, other)) {
      return true;
    }
    if (other.runtimeType != runtimeType) {
      return false;
    }

    return other is CrosshairBehavior &&
        other.activationMode == activationMode &&
        other.lineType == lineType &&
        other.lineDashArray == lineDashArray &&
        other.enable == enable &&
        other.lineColor == lineColor &&
        other.lineWidth == lineWidth &&
        other.shouldAlwaysShow == shouldAlwaysShow &&
        other.hideDelay == hideDelay;
  }

  @override
  int get hashCode {
    final List<Object?> values = <Object?>[
      activationMode,
      lineType,
      lineDashArray,
      enable,
      lineColor,
      lineWidth,
      shouldAlwaysShow,
      hideDelay,
    ];
    return Object.hashAll(values);
  }

  late CartesianStateProperties _stateProperties;

  void show(dynamic x, double y, [String coordinateUnit = 'point']) {
    final CrosshairBehaviorRenderer crosshairBehaviorRenderer = _stateProperties.crosshairBehaviorRenderer;
    final CrosshairRenderingDetails renderingDetails = CrosshairHelper.getRenderingDetails(crosshairBehaviorRenderer);
    renderingDetails.internalShow(x, y, coordinateUnit);
  }

  void showByIndex(int pointIndex) {
    final CrosshairBehaviorRenderer crosshairBehaviorRenderer = _stateProperties.crosshairBehaviorRenderer;
    final CrosshairRenderingDetails renderingDetails = CrosshairHelper.getRenderingDetails(crosshairBehaviorRenderer);
    if (validIndex(pointIndex, 0, renderingDetails.crosshairPainter!.chart)) {
      if (renderingDetails.crosshairPainter != null) {
        final List<CartesianSeriesRenderer> visibleSeriesRenderer =
            renderingDetails.crosshairPainter!.stateProperties.chartSeries.visibleSeriesRenderers;
        final SeriesRendererDetails seriesRendererDetails =
            SeriesHelper.getSeriesRendererDetails(visibleSeriesRenderer[0]);
        final List<CartesianChartPoint<dynamic>> dataPoints = getSampledData(seriesRendererDetails);
        if (pointIndex.abs() < seriesRendererDetails.dataPoints.length) {
          renderingDetails.crosshairPainter!
              .generateAllPoints(Offset(dataPoints[pointIndex].markerPoint!.x, dataPoints[pointIndex].markerPoint!.y));
        }
        renderingDetails.crosshairPainter!.canResetPath = false;
        renderingDetails.crosshairPainter!.stateProperties.repaintNotifiers['crosshair']!.value++;
      }
    }
  }

  void hide() {
    final CrosshairBehaviorRenderer crosshairBehaviorRenderer = _stateProperties.crosshairBehaviorRenderer;
    final CrosshairRenderingDetails renderingDetails = CrosshairHelper.getRenderingDetails(crosshairBehaviorRenderer);
    if (renderingDetails.crosshairPainter != null) {
      renderingDetails.crosshairPainter!.canResetPath = false;
      ValueNotifier<int>(renderingDetails.crosshairPainter!.stateProperties.repaintNotifiers['crosshair']!.value++);
      renderingDetails.crosshairPainter!.timer?.cancel();
      if (!_stateProperties.isTouchUp) {
        renderingDetails.crosshairPainter!.stateProperties.repaintNotifiers['crosshair']!.value++;
        renderingDetails.crosshairPainter!.canResetPath = true;
        renderingDetails.position = null;
      } else {
        if (!shouldAlwaysShow) {
          final double duration =
              (hideDelay == 0 && renderingDetails.crosshairPainter!.stateProperties.enableDoubleTap == true)
                  ? 200
                  : hideDelay;
          renderingDetails.crosshairPainter!.timer = Timer(Duration(milliseconds: duration.toInt()), () {
            renderingDetails.crosshairPainter!.stateProperties.repaintNotifiers['crosshair']!.value++;
            renderingDetails.crosshairPainter!.canResetPath = true;
            renderingDetails.position = null;
          });
        }
      }
    }
  }
}

class CrosshairBehaviorRenderer with ChartBehavior {
  CrosshairBehaviorRenderer(this._stateProperties) {
    _crosshairRenderingDetails = CrosshairRenderingDetails(_stateProperties);
  }

  final CartesianStateProperties _stateProperties;

  late CrosshairRenderingDetails _crosshairRenderingDetails;

  @override
  void onDoubleTap(double xPos, double yPos) => _crosshairRenderingDetails._crosshairBehavior.show(xPos, yPos, 'pixel');

  @override
  void onLongPress(double xPos, double yPos) => _crosshairRenderingDetails._crosshairBehavior.show(xPos, yPos, 'pixel');

  @override
  void onTouchDown(double xPos, double yPos) => _crosshairRenderingDetails._crosshairBehavior.show(xPos, yPos, 'pixel');

  @override
  void onTouchMove(double xPos, double yPos) => _crosshairRenderingDetails._crosshairBehavior.show(xPos, yPos, 'pixel');

  @override
  void onTouchUp(double xPos, double yPos) => _crosshairRenderingDetails._crosshairBehavior.hide();

  @override
  void onEnter(double xPos, double yPos) => _crosshairRenderingDetails._crosshairBehavior.show(xPos, yPos, 'pixel');

  @override
  void onExit(double xPos, double yPos) => _crosshairRenderingDetails._crosshairBehavior.hide();

  @override
  void onPaint(Canvas canvas) {
    if (_crosshairRenderingDetails.crosshairPainter != null) {
      _crosshairRenderingDetails.crosshairPainter!.drawCrosshair(canvas);
    }
  }
}

class CrosshairRenderingDetails {
  CrosshairRenderingDetails(this._stateProperties);

  final CartesianStateProperties _stateProperties;

  CgCartesianChart get _chart => _stateProperties.chart;

  CrosshairBehavior get _crosshairBehavior => _chart.crosshairBehavior;

  Offset? position;

  CrosshairPainter? crosshairPainter;

  bool isLongPressActivated = false;

  void drawLine(Canvas canvas, Paint? paint, int? seriesIndex) {
    assert(_crosshairBehavior.lineWidth >= 0, 'Line width value of crosshair should be greater than 0.');
    if (crosshairPainter != null && paint != null) {
      crosshairPainter!.drawCrosshairLine(canvas, paint, seriesIndex);
    }
  }

  Paint? linePainter(Paint paint) => crosshairPainter?.getLinePainter(paint);

  void internalShow(dynamic x, double y, [String coordinateUnit = 'point']) {
    final CrosshairBehaviorRenderer crosshairBehaviorRenderer = _stateProperties.crosshairBehaviorRenderer;
    if (coordinateUnit != 'pixel') {
      final SeriesRendererDetails seriesRendererDetails =
          SeriesHelper.getSeriesRendererDetails(_stateProperties.chartSeries.visibleSeriesRenderers[0]);
      final ChartAxisRendererDetails xAxisDetails = seriesRendererDetails.xAxisDetails!;
      final ChartLocation location = calculatePoint(
          (x is DateTime && xAxisDetails is! DateTimeCategoryAxisDetails)
              ? x.millisecondsSinceEpoch
              : ((x is DateTime && xAxisDetails is DateTimeCategoryAxisDetails)
                  ? xAxisDetails.labels.indexOf(xAxisDetails.dateFormat.format(x))
                  : ((x is String && xAxisDetails is CategoryAxisDetails) ? xAxisDetails.labels.indexOf(x) : x)),
          y,
          xAxisDetails,
          seriesRendererDetails.yAxisDetails!,
          seriesRendererDetails.stateProperties.requireInvertedAxis,
          seriesRendererDetails.series,
          seriesRendererDetails.stateProperties.chartAxis.axisClipRect,);
      x = location.x;
      y = location.y.truncateToDouble();
    }

    final CrosshairRenderingDetails renderingDetails = CrosshairHelper.getRenderingDetails(crosshairBehaviorRenderer);
    if (renderingDetails.crosshairPainter != null && x != null) {
      renderingDetails.crosshairPainter!.generateAllPoints(Offset(x.toDouble(), y));
      renderingDetails.crosshairPainter!.canResetPath = false;
      renderingDetails.crosshairPainter!.stateProperties.repaintNotifiers['crosshair']!.value++;
    }
  }
}

class CrosshairHelper {
  const CrosshairHelper._();

  static CrosshairRenderingDetails getRenderingDetails(CrosshairBehaviorRenderer renderer) {
    return renderer._crosshairRenderingDetails;
  }

  static void setStateProperties(CrosshairBehavior crosshairBehavior, CartesianStateProperties stateProperties) {
    crosshairBehavior._stateProperties = stateProperties;
  }
}
